package users

import (
	"context"
	"fmt"
	sqlbuilder "github.com/Masterminds/squirrel"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"product/common/validator"
)

var _ UsersModel = (*customUsersModel)(nil)

type (
	// UsersModel is an interface to be customized, add more methods here,
	// and implement the added methods in customUsersModel.
	UsersModel interface {
		usersModel
		// withSession 启用回话
		// session: 传入一个session
		withSession(session sqlx.Session) UsersModel
		// Trans 启用事务
		// fn: 传入一个函数，这个函数需要传入一个context和session
		// 这个函数的目的是，在函数内部，可以开启一个事务，然后执行fn函数，
		Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error
		// CheckPhoneExists 检查手机号是否已经存在
		// mobile: 手机号
		// 返回值：bool
		CheckPhoneExists(ctx context.Context, mobile string) (bool, error)
		// CheckEmailExists 检查邮箱是否已经存在
		// email: 邮箱
		// 返回值：bool
		CheckEmailExists(ctx context.Context, email string) (bool, error)
		// CheckUserNameExists 检查用户名是否已经存在
		// userName: 用户名
		// 返回值：bool
		CheckUserNameExists(ctx context.Context, userName string) (bool, error)
		// CheckValidUser 登录
		// userName: 用户名
		// password: 密码
		// 返回值：用户信息
		CheckValidUser(ctx context.Context, userName, password string) (*Users, error)
		// FindSalt 查找盐值
		// userName: 用户名
		// 返回值：盐值
		FindSalt(ctx context.Context, userName string) (string, error)
		// FindPageListByPage 分页查询
		// page: 页码
		// pageSize: 页大小
		// field: 排序字段
		// order: 排序规则
		// 返回值：用户列表
		FindPageListByPage(ctx context.Context, page, pageSize int64,
			conditions map[string]interface{},
			orders []string) ([]*Users, error)
		// ModifyUserInfo 修改用户信息
		// updateFields: 用户信息
		// 返回值：bool
		ModifyUserInfo(ctx context.Context, updateFields map[string]interface{}, id int64) (bool, error)
		generateSQL() sqlbuilder.SelectBuilder
	}

	customUsersModel struct {
		*defaultUsersModel
	}
)

func (m *customUsersModel) ModifyUserInfo(ctx context.Context, updateFields map[string]interface{}, id int64) (bool, error) {
	sql := sqlbuilder.Update(m.table).SetMap(updateFields).Where("ID = ?", id)
	query, values, errSql := sql.ToSql()
	if errSql != nil || validator.IsEmptyString(query) {
		return false, errSql
	}
	// 打印sql
	fmt.Println(" ModifyUserInfo sql:", query)
	_, err := m.conn.ExecCtx(ctx, query, values...)
	switch err {
	case nil:
		return true, nil
	default:
		return false, err
	}
}

func (m *customUsersModel) FindPageListByPage(ctx context.Context, page, pageSize int64,
	conditions map[string]interface{}, orders []string) ([]*Users, error) {
	sql := generateFindPageListByPageSql(conditions, orders).From(m.table)
	offset := (page - 1) * pageSize
	sql = sql.Offset(uint64(offset)).Limit(uint64(pageSize))
	query, values, errSql := sql.ToSql()
	if errSql != nil || validator.IsEmptyString(query) {
		return nil, errSql
	}
	fmt.Println("sql:", query)
	var resp []*Users
	err := m.conn.QueryRowsCtx(ctx, &resp, query, values...)
	switch err {
	case nil:
		return resp, nil
	case sqlx.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *customUsersModel) FindSalt(ctx context.Context, userName string) (string, error) {
	query, values, errSql := generateFindSaltSql(userName).From(m.table).Limit(1).ToSql()
	fmt.Println("sql:", query)
	if errSql != nil || validator.IsEmptyString(query) {
		return "", errSql
	}
	var salt string
	err := m.conn.QueryRowCtx(ctx, &salt, query, values...)
	switch err {
	case nil:
		return salt, nil
	case sqlx.ErrNotFound:
		return "", nil
	default:
		return "", err
	}
}

func (m *customUsersModel) CheckValidUser(ctx context.Context, userName, password string) (*Users, error) {
	sql := generateCheckValidUserSql(userName, password).From(m.table).Limit(1)
	query, args, errSql := sql.ToSql()
	if errSql != nil {
		return nil, errSql
	}
	var resp Users
	err := m.conn.QueryRowCtx(ctx, &resp, query, args...)
	switch err {
	case nil:
		return &resp, nil
	case sqlx.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *customUsersModel) CheckPhoneExists(ctx context.Context, mobile string) (bool, error) {
	sql := generateCheckPhoneExistsSql(mobile).From(m.table).Limit(1)
	query, values, sqlErr := sql.ToSql()
	if sqlErr != nil || validator.IsEmptyString(query) {
		return false, sqlErr
	}
	var id int64
	err := m.conn.QueryRowCtx(ctx, &id, query, values...)
	switch err {
	case nil:
		return id > 0, nil
	case sqlx.ErrNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (m *customUsersModel) CheckEmailExists(ctx context.Context, email string) (bool, error) {
	sql := generateCheckEmailExistsSql(email).From(m.table).Limit(1)
	query, values, sqlErr := sql.ToSql()
	if sqlErr != nil || validator.IsEmptyString(query) {
		return false, sqlErr
	}
	var id int64
	err := m.conn.QueryRowCtx(ctx, &id, query, values...)
	switch err {
	case nil:
		return id > 0, nil
	case sqlx.ErrNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (m *customUsersModel) CheckUserNameExists(ctx context.Context, userName string) (bool, error) {
	sql := generateCheckUserNameExistsSql(userName).From(m.table).Limit(1)
	query, values, sqlErr := sql.ToSql()
	if sqlErr != nil || validator.IsEmptyString(query) {
		return false, sqlErr
	}
	var id int64
	err := m.conn.QueryRowCtx(ctx, &id, query, values...)
	switch err {
	case nil:
		return id > 0, nil
	case sqlx.ErrNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (m *customUsersModel) Trans(ctx context.Context, fn func(context context.Context, session sqlx.Session) error) error {
	return m.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
		return fn(ctx, session)
	})
}

// NewUsersModel returns a model for the database table.
func NewUsersModel(conn sqlx.SqlConn) UsersModel {
	return &customUsersModel{
		defaultUsersModel: newUsersModel(conn),
	}
}

func (m *customUsersModel) withSession(session sqlx.Session) UsersModel {
	return NewUsersModel(sqlx.NewSqlConnFromSession(session))
}

// ################## 以下为私有方法 ##################
func (m *customUsersModel) generateSQL() sqlbuilder.SelectBuilder {
	return sqlbuilder.Select(usersRows).From(m.table)
}

// 生成查询盐值的sql
// userName: 用户名
// 返回值：sql语句
func generateFindSaltSql(userName string) sqlbuilder.SelectBuilder {
	sql := sqlbuilder.Select("PasswordSalt")
	if validator.IsChineseMobile(userName) {
		sql = sql.Where("Mobile = ?", userName)
	} else if validator.IsEmail(userName) {
		sql = sql.Where("Email = ?", userName)
	} else {
		sql = sql.Where("UserName = ?", userName)
	}
	return sql
}

// 生成检查用户名和密码是否匹配的sql
func generateCheckValidUserSql(userName, password string) sqlbuilder.SelectBuilder {
	sql := sqlbuilder.Select(usersRows)
	if validator.IsChineseMobile(userName) {
		sql = sql.Where("Mobile = ?", userName)
	} else if validator.IsEmail(userName) {
		sql = sql.Where("Email = ?", userName)
	} else {
		sql = sql.Where("UserName = ?", userName)
	}
	return sql.Where("Password = ?", password)
}

// 生成检查手机号是否存在的sql
func generateCheckPhoneExistsSql(phone string) sqlbuilder.SelectBuilder {
	sql := sqlbuilder.Select("ID")
	return sql.Where("Mobile = ?", phone)
}

// 生成检查邮箱是否存在的sql
func generateCheckEmailExistsSql(email string) sqlbuilder.SelectBuilder {
	sql := sqlbuilder.Select("ID")
	return sql.Where("Email = ?", email)
}

// 生成检查用户名是否存在的sql
func generateCheckUserNameExistsSql(userName string) sqlbuilder.SelectBuilder {
	sql := sqlbuilder.Select("ID")
	return sql.Where("UserName = ?", userName)
}

// 生成分页查询的sql
func generateFindPageListByPageSql(conditions map[string]interface{},
	orders []string) sqlbuilder.SelectBuilder {
	sql := sqlbuilder.Select(usersRows)
	if len(conditions) > 0 {
		for k, v := range conditions {
			sql = sql.Where(fmt.Sprintf("`%s` = ?", k), v)
		}
	}
	if len(orders) > 0 {
		sql = sql.OrderBy(orders...)
	}
	return sql

}

// ################## 私有方法结束 #################
